// Copyright 2008 and onwards Matt Burkhart, Mike Chan.
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.

#ifndef SWITCHFS_H__
#define SWITCHFS_H__

#include <fuse.h>
#include <pthread.h>
#include <string>
#include <vector>

#include "logger.h"
#include "source.h"

// SwitchFS exposes an interface to a SwitchFS file system. All methods are
// thread-safe except where explicitly noted.
class SwitchFS {
public:
  // Initialize a SwitchFS instance. Logger must not be NULL.
  SwitchFS(Logger* logger);

  // Add a data path to the file system. SwitchFS currently assumes all sources
  // have been added before any of the file io methods are called.
  void AddSource(const std::string& location);

  // Ensure that all sources have the same directory structure. This method is
  // not thread-safe and must be invoked before the filesystem is accessed. This
  // is invoked via -check command line flag. Recursive function that checks dir
  // structure of sources using DFS traversal.
  // Args:
  //   virtual_path - The SwitchFS path excluding the mount point. ie:
  //                  /mnt/sfs/<virtual_path>.
  // Returns:
  //   Returns 0 on success, error code from errno otherwise.
  int Check(const std::string& virtual_path, bool balance);

  // File system interface methods designed to interface with FUSE. For more
  // documentation, equivalents are all defined in fuse.h, alternatively see
  // "man 2 foo"

  // Get file system statistics.
  int statfs(const char* path, struct statvfs* buffer);

  // Check file access.
  int access(const char* path, int mode);

  // Get file attributes.
  int getattr(const char* path, struct stat* attributes);

  // Change file permissions.
  int chmod(const char* path, mode_t mode);

  // Change file ownership.
  int chown(const char* path, uid_t uid, gid_t gid);

  // Change file access and/or modification times.
  int utime(const char* path, struct utimbuf* buffer);

  // Create a directory.
  int mkdir(const char* path, mode_t mode);

  // Read a directory.
  int readdir(const char* path, void* buf, fuse_fill_dir_t filler,
	      off_t offset, struct fuse_file_info* fi);

  // Create a non-symlink, non-dir file.
  int mknod(const char* path, mode_t mode, dev_t dev);

  // Remove a file.
  int unlink(const char* path);

  // Remove a directory.
  int rmdir(const char* path);

  // Rename / move a file.
  int rename(const char* old_path, const char* new_path);

  // Open a file descriptor.
  int open(const char* path, struct fuse_file_info *fi);

  // Release all references to a given file descriptor.
  int release(const char* path, struct fuse_file_info* fi);

  // Read data from an open file.
  int read(const char* path, char* buffer, size_t size, off_t offset,
	   struct fuse_file_info* fi);

  // Write data to an open file.
  int write(const char* path, const char* buffer, size_t size, off_t offset,
	    struct fuse_file_info* fi);

  // Possibly flush cached data.
  int fsync(const char* path, int datasync, struct fuse_file_info* fi);

  // Change the size of a file.
  int truncate(const char* path, off_t size);

private:
  Logger& logger();

  // For the specified virtual file path, ensure that it has exactly the number
  // of replicas as specified by the user via the file replication directives
  // and / or the default replication amount. If the file does not have enough
  // physical replicas, more are made. If the file has too many physical
  // replicas, existing replicas are deleted.
  int ReplicateFile(const std::string& virtual_path);

  // For the specified virtual file path, redistribute its replicas across
  // drives as if the file was newly created according to the file allocation
  // policy. This has the effect of redistributing load across sources evenly.
  // Note that this does not check or enforce desired file replication levels.
  // However this could be extended to do so in the future.
  int BalanceFile(const std::string& virtual_path);

  // Choose count number of source locations to place the "next" new file(s).
  std::vector<Source*> AllocateSources(size_t count);

  // Return a vector of all of the sources this file is stored on.
  std::vector<Source*> GetFileSources(const std::string& virtual_path);

  // Return one of a files source(s).
  Source* GetFileSource(const std::string& virtual_path);

  // Return an vector of all of a file's real, physical path(s).
  std::vector<std::string> GetFilePaths(const std::string& virtual_path);

  // Return one of the file's real, physical path(s).
  std::string GetFilePath(const std::string& virtual_path);

  // Return the desired amount of replication (number of copies) for the
  // specified file. Note that a file must be specified here, not a directory.
  size_t GetDesiredReplication(const std::string& virtual_path);

  // The directive filename specifies the name of the files in which the user
  // may specify the replication amounts for each directory tree. For example,
  // ".backup". If no such file is found, GetDefaultReplicationLevel() is used.
  static std::string GetDirectiveFilename();

  // Return the default number of file copies to keep. The minimum valid result
  // value is 1.
  static size_t GetDefaultReplicationLevel();

  // The deleted prefix is the directory to place all files and directories
  // which have been deleted.
  static std::string GetDeletedPrefix();

  // Amount of space to reserve on each source location. Units in bytes.
  static unsigned long long GetBytesReserved();

  // The following are blocking calls to perform the associated file system
  // action on the specified physical file path(s). If the command was executed
  // and then exited with a successful status, 0 is returned. Otherwise, -1
  // indicates and error. Each method call is logged.
  int MovePhysicalFile(const std::string& physical_path_source,
                       const std::string& physical_path_dest);
  int CopyPhysicalFile(const std::string& physical_path_source,
                       const std::string& physical_path_dest);
  int RemovePhysicalFile(const std::string& physical_path);

  // Blocking call to execute the specified command with the specified
  // arguments. The command may be an absolute path or a file name in which case
  // the PATH environment variables are searched for the file. If the command
  // was executed and then exited with a successful status, 0 is returned.
  // Otherwise, -1 indicates and error.
  static int ExecCommand(const char* cmd, const char* const* args);

  std::vector<Source> sources_;
  pthread_rwlock_t sources_lock_;

  Logger* logger_;
};

#endif  // SWITCHFS_H__
